package com.iflytek.fpva.RedissonLocker;

import com.alibaba.fastjson.JSON;
import com.iflytek.fpva.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * Comments: 基于redisson实现的分布式锁AOP业务处理逻辑
 *
 * @author mjjiang3
 * @date 2020/04/20 13:08
 */
@Aspect
@Configuration
@Slf4j
public class DistributedLockAspect {

    @Value("${spring.application.name:default}:lock-key:")
    private String lockKey;

    private final RedissonClient redissonClient;

    /**
     * 预编译缓存
     */
    private final ConcurrentHashMap<String, SpelExpression> CACHE = new ConcurrentHashMap<>();

    public DistributedLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }


    /**
     * 尝试获取锁（如果锁不存在会加锁，加锁过程由Redission实现）
     *
     * @param joinPoint                JoinPoint
     * @param distributedRedissonLock DistributedRedissonLock
     */
    @Around("execution(* *..*.*(..)) && @annotation(distributedRedissonLock)")
    public Object before(ProceedingJoinPoint joinPoint, DistributedRedissonLock distributedRedissonLock) throws Throwable {
        String lockKey = getLockKey(joinPoint, distributedRedissonLock);

        if (StringUtils.isBlank(lockKey)) {
            log.error("获取分布式锁失败");
            return null;
        }
        String keyPre = StringUtils.isBlank(distributedRedissonLock.keyPre()) ? this.lockKey : distributedRedissonLock.keyPre();

        RLock lock = redissonClient.getLock(keyPre + lockKey);
        if (lock == null) {
            return null;
        }
        // 获取锁失败就抛异常
        try {
            // tryLock 尝试获取锁，当waitTime为0时直接无需等待，获取锁。推荐waitTime为0
            if (!lock.tryLock(distributedRedissonLock.waitTime(), distributedRedissonLock.expireSeconds(), TimeUnit.SECONDS)) {
                return null;
            }
        } catch (InterruptedException e) {
            log.error("分布式锁出现异常", e);
        }
        Object o;
        try {
            o = joinPoint.proceed();
        } catch (Throwable throwable) {
            // 当执行的方法抛出异常也进行主动释放锁
            try {
                lock.unlock();
                log.info("方法异常释放锁..." + lockKey);
            } catch (Exception e) {
                return null;
            }
            throw throwable;
        }
        // 方法执行完（无异常）主动释放锁
        try {
            lock.unlock();
            log.info("释放锁..." + lock);
        } catch (Exception e) {
            return null;
        }
        return o;
    }


    /**
     * 获取锁的key
     *
     * @param joinPoint       joinPoint
     * @param distributedLock distributedLock
     * @return String
     */
    private String getLockKey(JoinPoint joinPoint, DistributedRedissonLock distributedLock) {
        try {
            if (StringUtils.isBlank(distributedLock.value())) {
                throw new BusinessException("distributedLock value is invalid!");
            }
            // 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
            String signature = joinPoint.getSignature().toLongString();
            SpelExpression spelExpression = CACHE.get(signature);
            if (null == spelExpression) {
                SpelExpressionParser parser = new SpelExpressionParser();

                /*
                    获取spel上下文 {#xx.xx} 此处加上{%s}的目的是为了保证spel表达式的完整性，
                    以免在解析的时候程序将{xx}内容解析为 xx,把{}误当做占位符。
                    如果此处在使用组合键时不使用 {%s}，会导致在parseRaw抛出异常，使用单个参数键会导致解析的值为 #xx
                 */
                spelExpression = parser.parseRaw(String.format("{%s}", distributedLock.value()));
                CACHE.put(signature, spelExpression);
            }

            // 获取被拦截方法参数名列表(使用Spring支持类库)
            String[] paraNames = new LocalVariableTableParameterNameDiscoverer().
                    getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
            // 获取方法上的局部变量列表
            Object[] params = joinPoint.getArgs();
            if (paraNames == null || params == null) {
                log.error("paraNames:" + JSON.toJSONString(paraNames) + ", params:" + JSON.toJSONString(params));
                throw new BusinessException("方法参数异常");
            }

            // 组装spel上下文 每个局部变量名:每个局部变量值
            StandardEvaluationContext context = new StandardEvaluationContext();
            for (int i = 0; i < paraNames.length; i++) {
                context.setVariable(paraNames[i], params[i]);
            }
            return spelExpression.getValue(context, String.class);
        } catch (Throwable t) {
            log.error("分布式锁异常 但允许调用 以防影响业务", t);
            return null;
        }
    }

}
